home *** CD-ROM | disk | FTP | other *** search
- {*********************************************************}
- {* AABUFSTM.PAS *}
- {* Copyright (c) Julian M Bucknall 1997, 1999 *}
- {* All rights reserved. *}
- {*********************************************************}
- {* Buffered Stream Class for use with any stream *}
- {*********************************************************}
-
- {Note: this unit is released as freeware. In other words, you are free
- to use this unit in your own applications, however I retain all
- copyright to the code. JMB}
-
- unit AABufStm;
-
- {Notes: The TaaBufferedStream class provides a method to buffer data
- to/from any stream. Since it is a TStream descendant itself,
- it can be used in place of any stream. This makes it easy to
- buffer THandleStream or TFileStream instancves, for example.
- The stream which needs to be buffered is passed as a parameter
- to the Create constructor. The TaaBufferedStream object merely
- provides a buffer between the user and the stream that holds
- the data.
-
- In Delphi 1 and 2, TStream did not provide a SetSize method;
- the Size property was read only. Some descendants of TStream
- did (eg, TMemoryStream), and with the file-based ones writing
- a set size routine is easy. Since TaaBufferedStream will
- sometimes set the size of the underlying stream, it provides
- an event to call to do so. Delphi 3 and later do not require
- this event to be set.}
-
-
- interface
-
- uses
- SysUtils,
- Classes;
-
- {$IFDEF VER100}
- {$DEFINE CanSetSize}
- {$ENDIF}
- {$IFDEF VER120}
- {$DEFINE CanSetSize}
- {$ENDIF}
-
- type
- {$IFDEF Windows}
- TbsMemSize = word; {Memory size type}
- {$ELSE}
- TbsMemSize = integer;
- {$ENDIF}
-
- type
- TaaSetSize = procedure (aStream : TStream; aNewSize : Longint);
- {-procedure prototype for setting the size of a stream}
-
- type
- TaaBufferedStream = class(TStream)
- protected {private}
- bsPage : PByteArray; {buffer}
- bsPageSize : TbsMemSize; {size of buffer (multiple of 1K)}
- bsPageStart : Longint; {start of buffer as offset in stream}
- bsPosInPage : TbsMemSize; {current position in buffer}
- bsByteCount : TbsMemSize; {count of valid bytes in buffer}
- bsSize : Longint; {count of bytes in stream}
- bsDirty : boolean; {whether the buffer is dirty or not}
- bsStream : TStream; {actual stream containing data}
- bsSetSize : TaaSetSize;
- protected
- procedure bsReadBuffer;
- procedure bsWriteBuffer;
-
- procedure SetSize(aNewSize : Longint);
- {$IFDEF CanSetSize} override; {$ENDIF}
- {-set the stream size}
-
- public
- constructor Create(aStream : TStream; aBufSize : TbsMemSize);
- {-create the buffered stream}
- destructor Destroy; override;
- {-destroy the buffered stream}
-
- function Read(var Buffer; Count : Longint) : Longint; override;
- {-read from the stream into a buffer}
- function Write(const Buffer; Count : Longint) : Longint; override;
- {-write to the stream from a buffer}
- function Seek(Offset : Longint; Origin : Word) : Longint; override;
- {-seek to a particular point in the stream}
- procedure Commit;
- {-ensures that all buffered data is flushed to disk}
-
- property OnSetStreamSize : TaaSetSize
- read bsSetSize write bsSetSize;
- {-event to set the size of the stream}
- property Stream : TStream read bsStream;
-
- end;
-
- implementation
-
- uses
- {$IFDEF Windows}
- WinTypes, WinProcs;
- {$ELSE}
- Windows;
- {$ENDIF}
-
- {===Helper routines==================================================}
- procedure RaiseException(const S : string);
- begin
- raise Exception.Create(S);
- end;
- {====================================================================}
-
-
- {===TaaBufferedStream================================================}
- constructor TaaBufferedStream.Create(aStream : TStream;
- aBufSize : TbsMemSize);
- var
- ActBufSize : Longint;
- begin
- inherited Create;
- {save the actual stream}
- bsStream := aStream;
- {round up the buffer size to a multiple of 1K and a maximum of 32K}
- ActBufSize := (Longint(aBufSize) + 1023) and $FFFFFC00;
- if (ActBufSize > 32 * 1024) then
- bsPageSize := 32 * 1024
- else
- bsPageSize := ActBufSize;
- {create the buffer}
- GetMem(bsPage, bsPageSize);
- {set the page/buffer variables to the start of the stream}
- bsPosInPage := 0;
- bsByteCount := 0;
- bsPageStart := 0;
- bsDirty := false;
- bsSize := aStream.Size;
- end;
- {--------}
- destructor TaaBufferedStream.Destroy;
- begin
- {destroy the buffer, after writing it to the actual stream}
- if (bsPage <> nil) then begin
- Commit;
- FreeMem(bsPage, bsPageSize);
- end;
- {let our ancestor clean up}
- inherited Destroy;
- end;
- {--------}
- procedure TaaBufferedStream.bsReadBuffer;
- var
- SeekResult : Longint;
- begin
- SeekResult := bsStream.Seek(bsPageStart, 0);
- if (SeekResult = -1) then
- RaiseException('TaaBufferedStream.bsReadBuffer: seek failed');
- bsByteCount := bsStream.Read(bsPage^, bsPageSize);
- if (bsByteCount <= 0) then
- RaiseException('TaaBufferedStream.bsReadBuffer: read failed');
- end;
- {--------}
- procedure TaaBufferedStream.bsWriteBuffer;
- var
- SeekResult : Longint;
- BytesWrit : Longint;
- begin
- SeekResult := bsStream.Seek(bsPageStart, 0);
- if (SeekResult = -1) then
- RaiseException('TaaBufferedStream.bsWriteBuffer: seek failed');
- BytesWrit := bsStream.Write(bsPage^, bsByteCount);
- if (BytesWrit <> bsByteCount) then
- RaiseException('TaaBufferedStream.bsWriteBuffer: write failed');
- end;
- {--------}
- procedure TaaBufferedStream.Commit;
- begin
- if bsDirty then begin
- bsWriteBuffer;
- bsDirty := false;
- end;
- end;
- {--------}
- function TaaBufferedStream.Read(var Buffer; Count : Longint) : Longint;
- var
- BufAsBytes : TByteArray absolute Buffer;
- BufInx : Longint;
- BytesToGo : Longint;
- BytesToRead : integer;
- begin
- {reading is complicated by the fact we can only read in chunks of
- bsPageSize: we need to partition out the overall read into a
- read from part of the buffer, zero or more reads from complete
- buffers and then a possible read from part of a buffer}
-
- {$IFDEF Windows}
- {in Delphi 1 we do not support reads greater than 65535 bytes}
- if (Count > $FFFF) then
- RaiseException('TaaBufferedStream.Read: requested too many bytes');
- {$ENDIF}
-
- {calculate the actual number of bytes we can read - this depends on
- the current position and size of the stream as well as the number
- of bytes requested}
- BytesToGo := Count;
- if (bsSize < (bsPageStart + bsPosInPage + Count)) then
- BytesToGo := bsSize - (bsPageStart + bsPosInPage);
- if (BytesToGo <= 0) then begin
- Result := 0;
- Exit;
- end;
- {remember to return the result of our calculation}
- Result := BytesToGo;
-
- {initialise the byte index for the caller's buffer}
- BufInx := 0;
- {is there anything in the buffer? if not, go read something from
- the actual stream}
- if (bsByteCount = 0) then
- bsReadBuffer;
- {calculate the number of bytes we can read prior to the loop}
- BytesToRead := bsByteCount - bsPosInPage;
- if (BytesToRead > BytesToGo) then
- BytesToRead := BytesToGo;
- {copy from the stream buffer to the caller's buffer}
- Move(bsPage^[bsPosInPage], BufAsBytes[BufInx], BytesToRead);
- {calculate the number of bytes still to read}
- dec(BytesToGo, BytesToRead);
-
- {while we have bytes to read, read them}
- while (BytesToGo > 0) do begin
- {advance the byte index for the caller's buffer}
- inc(BufInx, BytesToRead);
- {as we've exhausted this buffer-full, advance to the next, check
- to see whether we need to write the buffer out first}
- if bsDirty then begin
- bsWriteBuffer;
- bsDirty := false;
- end;
- inc(bsPageStart, bsPageSize);
- bsPosInPage := 0;
- bsReadBuffer;
- {calculate the number of bytes we can read in this cycle}
- BytesToRead := bsByteCount;
- if (BytesToRead > BytesToGo) then
- BytesToRead := BytesToGo;
- {copy from the stream buffer to the caller's buffer}
- Move(bsPage^, BufAsBytes[BufInx], BytesToRead);
- {calculate the number of bytes still to read}
- dec(BytesToGo, BytesToRead);
- end;
- {remember our new position}
- inc(bsPosInPage, BytesToRead);
- if (bsPosInPage = bsPageSize) then begin
- inc(bsPageStart, bsPageSize);
- bsPosInPage := 0;
- bsByteCount := 0;
- end;
- end;
- {--------}
- function TaaBufferedStream.Seek(Offset : Longint;
- Origin : Word) : Longint;
- var
- NewPageStart : Longint;
- NewPos : Longint;
- begin
- {calculate the new position}
- case Origin of
- soFromBeginning : NewPos := Offset;
- soFromCurrent : NewPos := bsPageStart + bsPosInPage + Offset;
- soFromEnd : NewPos := bsSize + Offset;
- else
- NewPos := 0;
- RaiseException('TaaBufferedStream.Seek: invalid origin');
- end;
- if (NewPos < 0) or (NewPos > bsSize) then
- RaiseException('TaaBufferedStream.Seek: invalid new position');
- {calculate which page of the file we need to be at}
- NewPageStart := NewPos and not(pred(longint(bsPageSize)));
- {if the new page is different than the old, mark the buffer as being
- ready to be replenished, and if need be write out any dirty data}
- if (NewPageStart <> bsPageStart) then begin
- if bsDirty then begin
- bsWriteBuffer;
- bsDirty := false;
- end;
- bsPageStart := NewPageStart;
- bsByteCount := 0;
- end;
- {save the new position}
- bsPosInPage := NewPos - NewPageStart;
- Result := NewPos;
- end;
- {--------}
- procedure TaaBufferedStream.SetSize(aNewSize : Longint);
- begin
- {save the new size and alter the position if required}
- bsSize := aNewSize;
- if ((bsPageStart + bsPosInPage) > aNewSize) then
- Seek(0, soFromEnd);
- {now set the size of the actual stream}
- if Assigned(bsSetSize) then
- bsSetSize(bsStream, aNewSize)
- else
- {$IFDEF CanSetSize}
- bsStream.Size := aNewSize;
- {$ELSE}
- RaiseException('TaaBufferedStream.SetSize: cannot set size of underlying stream');
- {$ENDIF}
- end;
- {--------}
- function TaaBufferedStream.Write(const Buffer; Count : Longint) : Longint;
- var
- BufAsBytes : TByteArray absolute Buffer;
- BufInx : Longint;
- BytesToGo : Longint;
- BytesToWrite: integer;
- begin
- {writing is complicated by the fact we write in chunks of
- bsPageSize: we need to partition out the overall write into a
- write from part of the buffer, zero or more writes from complete
- buffers and then a possible write from part of a buffer}
-
- {$IFDEF Windows}
- {in Delphi 1 we do not support writes greater than 65535 bytes}
- if (Count > $FFFF) then
- RaiseException('TaaBufferedStream.Write: requested too many bytes');
- {$ENDIF}
-
- {when we write to this stream we always assume that we can write the
- requested number of bytes: if we can't (eg, the disk is full) we'll
- get an exception somewhere eventually}
- BytesToGo := Count;
- {remember to return the result of our calculation}
- Result := BytesToGo;
-
- {initialise the byte index for the caller's buffer}
- BufInx := 0;
- {is there anything in the buffer? if not, go try read a block from
- the actual stream - we might be overwriting existing data rather
- than appending data to the end of the stream}
- if (bsByteCount = 0) and (bsSize > bsPageStart) then
- bsReadBuffer;
- {calculate the number of bytes we can write prior to the loop}
- BytesToWrite := bsPageSize - bsPosInPage;
- if (BytesToWrite > BytesToGo) then
- BytesToWrite := BytesToGo;
- {copy from the caller's buffer to the stream buffer}
- Move(BufAsBytes[BufInx], bsPage^[bsPosInPage], BytesToWrite);
- {mark our stream buffer as requiring a save to the actual stream,
- note that this will suffice for the rest of the routine as well: no
- inner routine will turn off the dirty flag}
- bsDirty := true;
- {calculate the number of bytes still to write}
- dec(BytesToGo, BytesToWrite);
-
- {while we have bytes to write, write them}
- while (BytesToGo > 0) do begin
- {advance the byte index for the caller's buffer}
- inc(BufInx, BytesToWrite);
- {as we've filled this buffer, write it out to the actual stream
- and advance to the next buffer, reading it if required}
- bsByteCount := bsPageSize;
- bsWriteBuffer;
- inc(bsPageStart, bsPageSize);
- bsPosInPage := 0;
- bsByteCount := 0;
- if (bsSize > bsPageStart) then
- bsReadBuffer;
- {calculate the number of bytes we can write in this cycle}
- BytesToWrite := bsPageSize;
- if (BytesToWrite > BytesToGo) then
- BytesToWrite := BytesToGo;
- {copy from the caller's buffer to our buffer}
- Move(BufAsBytes[BufInx], bsPage^, BytesToWrite);
- {calculate the number of bytes still to write}
- dec(BytesToGo, BytesToWrite);
- end;
- {remember our new position}
- inc(bsPosInPage, BytesToWrite);
- {make sure the count of valid bytes is correct}
- if (bsByteCount < bsPosInPage) then
- bsByteCount := bsPosInPage;
- {make sure the stream size is correct}
- if (bsSize < (bsPageStart + bsByteCount)) then
- bsSize := bsPageStart + bsByteCount;
- {if we're at the end of the buffer, write it out and advance to the
- start of the next page}
- if (bsPosInPage = bsPageSize) then begin
- bsWriteBuffer;
- bsDirty := false;
- inc(bsPageStart, bsPageSize);
- bsPosInPage := 0;
- bsByteCount := 0;
- end;
- end;
- {====================================================================}
-
- end.
-